package jsr292.java.lang.invoke;

import jsr292.weaver.Capabilities;
import jsr292.weaver.Debug;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerSnippets;
import jsr292.weaver.opt.OptimizerVarAllocator;

class MHInserter extends MethodHandle {
    private final MethodHandle mh;
    private final Object[] values;
    
    public MHInserter(MethodHandle mh,  Object[] values, MethodType methodType) {
        super(methodType);
        this.mh = mh;
        
        assert values.length>0;
        this.values = values;
    }
    
    @Override
    public Object $invoke$() throws Throwable {
        Object[] values = this.values;
        switch(values.length) {
        case 1:
          return mh.$invoke$(values[0]);
        case 2:
          return mh.$invoke$(values[0], values[1]);
        case 3:
          return mh.$invoke$(values[0], values[1], values[2]);
        case 4:
          return mh.$invoke$(values[0], values[1], values[2], values[3]);
        case 5:
          return mh.$invoke$(values[0], values[1], values[2], values[3], values[4]);
        case 6:
          return mh.$invoke$(values[0], values[1], values[2], values[3], values[4], values[5]);
        case 7:
          return mh.$invoke$(values[0], values[1], values[2], values[3], values[4], values[5], values[6]);
        }
        return mh.$invoke$(values);
    }
    
    @Override
    public Object $invoke$(Object o1) throws Throwable {
      Object[] values = this.values;
      int length = values.length;
      switch(length) {
      case 1:
        return mh.$invoke$(values[0], o1);
      case 2:
        return mh.$invoke$(values[0], values[1], o1);
      case 3:
        return mh.$invoke$(values[0], values[1], values[2], o1);
      case 4:
        return mh.$invoke$(values[0], values[1], values[2], values[3], o1);
      case 5:
        return mh.$invoke$(values[0], values[1], values[2], values[3], values[4], o1);
      case 6:
        return mh.$invoke$(values[0], values[1], values[2], values[3], values[4], values[5], o1);
      }
      Object[] array=ArrayUtils.copyOf(values, length + 1);
      array[length] = o1;
      return mh.$invoke$(array);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2) throws Throwable {
      Object[] values = this.values;
      int length = values.length;
      switch(length) {
      case 1:
        return mh.$invoke$(values[0], o1, o2);
      case 2:
        return mh.$invoke$(values[0], values[1], o1, o2);
      case 3:
        return mh.$invoke$(values[0], values[1], values[2], o1, o2);
      case 4:
        return mh.$invoke$(values[0], values[1], values[2], values[3], o1, o2);
      case 5:
        return mh.$invoke$(values[0], values[1], values[2], values[3], values[4], o1, o2);
      }
      Object[] array=ArrayUtils.copyOf(values, length + 2);
      array[length] = o1;
      array[length + 1] = o2;
      return mh.$invoke$(array);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
      Object[] values = this.values;
      int length = values.length;
      switch(length) {
      case 1:
        return mh.$invoke$(values[0], o1, o2, o3);
      case 2:
        return mh.$invoke$(values[0], values[1], o1, o2, o3);
      case 3:
        return mh.$invoke$(values[0], values[1], values[2], o1, o2, o3);
      case 4:
        return mh.$invoke$(values[0], values[1], values[2], values[3], o1, o2, o3);
      }
      Object[] array=ArrayUtils.copyOf(values, length + 3);
      array[length] = o1;
      array[length + 1] = o2;
      array[length + 2] = o3;
      return mh.$invoke$(array);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4) throws Throwable {
      Object[] values = this.values;
      int length = values.length;
      switch(length) {
      case 1:
        return mh.$invoke$(values[0], o1, o2, o3, o4);
      case 2:
        return mh.$invoke$(values[0], values[1], o1, o2, o3, o4);
      case 3:
        return mh.$invoke$(values[0], values[1], values[2], o1, o2, o3, o4);
      }
      Object[] array=ArrayUtils.copyOf(values, length + 4);
      array[length] = o1;
      array[length + 1] = o2;
      array[length + 2] = o3;
      array[length + 3] = o4;
      return mh.$invoke$(array);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5) throws Throwable {
      Object[] values = this.values;
      int length = values.length;
      switch(length) {
      case 1:
        return mh.$invoke$(values[0], o1, o2, o3, o4, o5);
      case 2:
        return mh.$invoke$(values[0], values[1], o1, o2, o3, o4, o5);
      }
      Object[] array=ArrayUtils.copyOf(values, length + 4);
      array[length] = o1;
      array[length + 1] = o2;
      array[length + 2] = o3;
      array[length + 3] = o4;
      array[length + 4] = o5;
      return mh.$invoke$(array);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Throwable {
      Object[] values = this.values;
      int length = values.length;
      if (length == 1) {
        return mh.$invoke$(values[0], o1, o2, o3, o4, o5, o6);
      }
      Object[] array=ArrayUtils.copyOf(values, length + 5);
      array[length] = o1;
      array[length + 1] = o2;
      array[length + 2] = o3;
      array[length + 3] = o4;
      array[length + 4] = o5;
      array[length + 5] = o6;
      return mh.$invoke$(array);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Throwable {
      Object[] values = this.values;
      int length = values.length;
      Object[] array=ArrayUtils.copyOf(values, length + 5);
      array[length] = o1;
      array[length + 1] = o2;
      array[length + 2] = o3;
      array[length + 3] = o4;
      array[length + 4] = o5;
      array[length + 5] = o6;
      array[length + 6] = o7;
      return mh.$invoke$(array);
    }
    
    @Override
    public Object $invoke$(Object... rest) throws Throwable {
      Object[] values = this.values;
      int length = values.length;
      int restLength = rest.length;
      Object[] array=ArrayUtils.copyOf(values, length + restLength);
      System.arraycopy(rest, 0, array, length, restLength);
      return mh.$invoke$(array);
    }

    static int[] getInsertOrder(int index, int valuesLength, int length) {
      int[] orders = new int[length];
      for(int i=0; i<index; i++) {
        orders[i] =  i + valuesLength;
      }
      for(int i=index; i<index + valuesLength; i++) {
        orders[i] = i - index;
      }
      for(int i=index + valuesLength; i<length; i++) {
        orders[i] = i;
      }
      return orders;
    }
    
    
    
    @Override
    public boolean isWeavable(Class<?> classBeingRedefined) {
        if (!Capabilities.canDefine()) {
            if (Debug.DEBUG) System.err.println("inserter requires define capability");
            return false;
        }
        
        MethodType methodType = mh.methodType;
        for(int i=0; i<values.length;i++) {
            if (OptimizerSnippets.getBindType(classBeingRedefined, methodType.parameterTypes[i], values[i]) == null) {
                //System.out.println("inserter: bind type null "+values[i]);
		if (Debug.DEBUG) System.err.println("inserter can't bind value "+values[i]);
		return false;
	    }
	}
        return mh.isWeavable(classBeingRedefined);
    }
    @Override
    public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
        if (Debug.DEBUG) {
            Debug.checkAllocator(allocator, this);
        }
        
        MethodType methodType = mh.methodType;
        
        Class<?>[] boundTypes = new Class<?>[values.length];
        for(int i=0; i<values.length;i++) {
            boundTypes[i] = OptimizerSnippets.getBindType(context.getClassBeingRedefined(), methodType.parameterTypes[i], values[i]);    
        }
	OptimizerSnippets.insert(context, allocator, mh, values, boundTypes);
    }
}
